
import warnings
warnings.filterwarnings("ignore", category=DeprecationWarning, message="sipPyTypeDict() is deprecated")
import cv2
import queue
import torch
from PyQt5.QtCore import QThread, pyqtSignal
from PyQt5.QtGui import QImage
from PyQt5.QtSql import QSqlQuery, QSqlDatabase
from PYQT_db.config import DATABASE_PATH


class cameraThread(QThread):
    send_result_img = pyqtSignal(QImage)
    send_original_frame = pyqtSignal(QImage)
    send_face_feature_box = pyqtSignal(list, list, QImage)

    def __init__(self, yolov8_model,facenet_model):
        super().__init__()
        self.running = True
        # 加载 YOLO 模型，准备预测
        self.yolov8_model = yolov8_model
        # 加载 FaceNet 模型，用于提取人脸特征
        self.facenet_model = facenet_model
        # 用于存储多个人脸的边界框坐标列表
        self.face_boxes = []
        # 用于存储多个人脸的特征列表
        self.face_features = []

    def run(self):
        self.cap = cv2.VideoCapture(0)
        if not self.cap.isOpened():
            print("无法打开摄像头，线程终止")
            self.running = False  # 停止线程
            return
        while self.running and self.cap.isOpened():
            # 注意 frame 是 numpy 数组， 使用信号传递参数的时候要注意信号参数的定义
            success, self.frame = self.cap.read()
            # 如果读取成功
            if success:
                original_frame = self.frame.copy()
                # 处理原图并传输
                h, w, ch = original_frame.shape
                original_frame_bytes_per_line = ch * w
                original_frame = QImage(original_frame.data, w, h, original_frame_bytes_per_line, QImage.Format_BGR888)
                self.send_original_frame.emit(original_frame)
                # 使用 YOLOv8 模型对当前帧进行预测，conf是置信度阈值
                results = self.yolov8_model.predict(self.frame, conf=0.7)
                # 获取检测结果中的边界框信息
                boxes = results[0].boxes
                # 清空两个列表
                self.face_boxes.clear()
                self.face_features.clear()
                # 遍历检测到的人脸框列表
                for box in boxes:
                    # 解包人脸框的坐标信息，xyxy是一个包含左上角和右下角坐标的列表
                    x1, y1, x2, y2 = box.xyxy[0].tolist()
                    # 提取类别标签，这里类别 0 代表人脸
                    cls = box.cls[0].tolist()
                    # 如果类别标签为0（即为人脸）
                    if cls == 0:
                        # 在原始帧上画一个红色的框来标记人脸
                        cv2.rectangle(self.frame, (int(x1), int(y1)), (int(x2), int(y2)), (0, 0, 255), 2)
                        # 从原始帧中裁剪出人脸图像区域
                        face_img = self.frame[int(y1):int(y2), int(x1):int(x2)]
                        # 创建一个列表包含边界框坐标, 如果一张图上有两张人脸，结果[[x1, y1], [x2, y2]]
                        self.face_boxes.append([x1, y1])
                        # 检查裁剪出的人脸图像是否非空
                        if face_img.shape[0] > 0 and face_img.shape[1] > 0:
                            # 对裁剪出的人脸图像进行预处理
                            face_tensor = self.preprocess_face_img(face_img)
                            # 从预处理后的人脸图像中提取特征
                            face_feature = self.extract_face_feature(face_tensor)
                            self.face_features.append(face_feature)

                # 通过信号传输画红框的图像
                # BGR -> RGB
                rgb_img = cv2.cvtColor(self.frame, cv2.COLOR_BGR2RGB)
                # 转换成 QImage
                rgb_img = QImage(rgb_img.data, rgb_img.shape[1], rgb_img.shape[0], QImage.Format_RGB888)
                # 使用信号传递 frame
                self.send_result_img.emit(rgb_img)




                self.send_face_feature_box.emit(self.face_boxes, self.face_features, rgb_img)

        # 线程结束，释放摄像头资源
        self.cap.release()

    def preprocess_face_img(self, face_img):
        """
        对人脸图像进行预处理
        :param face_img: 需要处理的图像
        :return:返回处理图像后的 PyTorch 张量
        """
        # 将BGR图像转换为RGB图像
        face_img = cv2.cvtColor(face_img, cv2.COLOR_BGR2RGB)
        # 定义 FaceNet 模型所需的输入图像尺寸
        required_size = (160, 160)
        # 使用OpenCV调整图像尺寸到模型期望的尺寸
        face_img = cv2.resize(face_img, required_size)
        # 将图像数据转换为 PyTorch 张量
        # permute(2, 0, 1)将图像的维度从HWC (高度、宽度、通道) 转换为CHW (通道、高度、宽度)
        # float()将张量数据类型转换为浮点数
        # 将图像转换为Tensor格式，并归一化
        # unsqueeze(0)在张量前面添加一个批次维度，因为模型期望输入是4维的 [B, C, H, W]
        face_tensor = (torch.tensor(face_img).permute(2, 0, 1).float() / 255.0).unsqueeze(0)
        # 返回图像归一化后的 PyTorch 张量
        return face_tensor

    def extract_face_feature(self, face_tensor):
        """
        提取人脸特征
        :param face_tensor: 图像预处理后的 PyTorch 张量
        :return: 返回 face_embedding 人脸特征
        """
        # 使用 torch.no_grad() 上下文管理器，表示告诉 PyTorch 不需要计算梯度
        # 这通常用于推理阶段，可以减少内存消耗和提高速度
        with torch.no_grad():
            # 将处理后的图像张量传递给 FaceNet 模型以提取特征
            face_embedding = self.facenet_model(face_tensor)
            # 对提取的人脸特征进行L2归一化
            # 计算特征向量的L2范数
            l2_norm = torch.norm(face_embedding, p=2, dim=1, keepdim=True)
            # 将特征向量除以其L2范数进行归一化
            face_embedding_normalized = face_embedding.div(l2_norm)
        # 将得到的特征张量移动到CPU上，并转换为 NumPy 数组
        # 这一步是为了后续处理，如保存特征或进行其他非 PyTorch 操作
        return face_embedding_normalized.cpu().numpy()

    def stop(self):
        # 线程停止标志位
        self.running = False
        self.wait()  # 等待线程结束
        if hasattr(self, 'cap'):
            self.cap.release()


class MySqlThread(QThread):
    #定义操作常量信号表示要实现的功能
    INSERT_NEW = 1  # 表示插入数据
    SELECT_BY_KEYWORD = 2  # 表示查询的命令
    SELECT_FACE_FEATURE = 3  #表示查询人脸特征的命令
    UPDATE_SORT = 4  # # 表示重新排序，刷新

    #定义信号用于和qt界面的通信
    query_result_signal = pyqtSignal(list)#发送列表数据,查询结果
    signal_sql_data = pyqtSignal(dict)#发送字典数据，数据库的人脸特征这一行的数据的信号
    finished_signal = pyqtSignal()#仅发送完成线程通知，更新 界面 model 的信号

    """初始化数据库"""
    def __init__(self):
        super().__init__()
        #初始化队列，并限制大小以防内存过度使用
        self.q_deal_sql_cmd = queue.Queue(5)
        #线程运行标志
        self.running = True

        #连接数据库
        self.db = QSqlDatabase.addDatabase('QSQLITE')
        self.db.setDatabaseName(DATABASE_PATH)

        #初始化数据库连接
        if self.db.open():
            print("已连接到数据库")
            # self._create_table_if_not_exists()#判断表是否存在，不存在调用函数创建表
            self.query = QSqlQuery(self.db)#创建查询对象
        else:
            print(f'数据库连接失败: {self.db.lastError().text()}')
            self.query = None

    # def _create_table_if_not_exists(self):
    #     #用查询对象执行sql语句
    #     table_exists = QSqlQuery("SELECT name FROM sqlite_master WHERE type='table' AND name='FaceDatabase1'")
    #     """通过next()方法移动到第一条记录：
    #     如果返回True：表示查询到匹配记录（表存在）
    #     如果返回False：表示没有匹配记录（表不存在）"""
    #     if not table_exists.next():
    #         create_table_query = QSqlQuery()
    #         #exec_() 方法返回布尔值：True 表示执行成功，False 表示失败
    #         if not create_table_query.exec_("""
    #                        CREATE TABLE FaceDatabase (
    #                            序号 INTEGER PRIMARY KEY AUTOINCREMENT,
    #                            姓名 TEXT NOT NULL,
    #                            性别 TEXT,
    #                            年龄 INTEGER,
    #                            学号 INTEGER,
    #                            录入时间 TEXT,
    #                            照片 BLOB,
    #                            人脸特征 BLOB
    #                        )
    #                    """):
    #             print('创建失败')
    #     else:
    #         print("表已存在")

    def run(self):
        """主线程，处理队列中的命令"""
        while self.running and self.query:
            q_data = self.q_deal_sql_cmd.get()
            cmd = q_data['cmd']
            #如果q_data中存在'content'键，则返回对应的值
            #如果不存在'content'键，则返回默认值{}（空字典）
            content = q_data.get('content',{})

            #根据命令执行相对应的操作
            self._process_command(cmd, content)

    def _process_command(self, cmd, content):
        """处理具体的数据库命令"""
        if cmd == self.INSERT_NEW:
            #cmd等于1调用执行插入命令的函数
            self.handle_insert(content)

        elif cmd == self.SELECT_BY_KEYWORD and 'name' in content:
            # cmd等于2且命令里面有名字（按名字查询）调用执行查询命令的函数
            self.handle_select_by_keyword(content)

        elif cmd == self.SELECT_FACE_FEATURE:
            #cmd等于3调用执行根据人脸特征查找命令的函数
            self.handle_select_face_feature(content)

        elif cmd == self.UPDATE_SORT:
            # cmd等于4调用执行刷新命令的函数
            self.handle_update_sort(content)

    def handle_insert(self, content):
        #prepare()方法预编译了一条插入数据的 SQL 语句,参数对位，
        # 后续通过 bindValue() 绑定数据，更安全插入表中（防止用户输入特殊字符）
        self.query.prepare("""
            INSERT INTO facedatabase 
            (姓名, 性别, 年龄, 学号, 录入时间, 照片, 人脸特征) 
            VALUES (:name, :sex, :age, :studentID, :time, :img, :facefeature)
        """)
        #绑定参数
        for key, value in content.items():
            #与:name保持一致
            self.query.bindValue(f":{key}", value)

        # 执行插入
        if self.query.exec_():
            print('数据保存成功')
        else:
            print(f'插入错误: {self.query.lastError().text()}')

    def handle_select_by_keyword(self, content):
        self.query.prepare("SELECT * FROM FaceDatabase WHERE 姓名 LIKE :keyword LIMIT 1")
        self.query.bindValue(":keyword", f"%{content['name'].strip()}%")
        result = []

        if self.query.exec() and self.query.first():
            # 获取所有字段值
            record_count = self.query.record().count()
            row_data = [self.query.value(i) for i in range(record_count)]
            result.append(row_data)

            # 发射查询结果信号,列表信号
        self.query_result_signal.emit(result)

    def handle_select_face_feature(self, content):
        if self.query.exec_("SELECT * FROM FaceDatabase"):
            data = []
            #遍历所有记录
            while self.query.next():
                record = (
                    self.query.value(1),  # 姓名
                    self.query.value(2),  # 性别
                    self.query.value(3),  # 年龄
                    self.query.value(4),  # 学号
                    self.query.value(7)  # 人脸特征
                )
                data.append(record)
            # 发射数据信号，字典
            self.signal_sql_data.emit({self.SELECT_FACE_FEATURE: data})

    def handle_update_sort(self, content):
        #处理更新（重新建表）
        """事务特性：开启事务后，执行的所有 SQL 操作（如插入、更新、删除）
            不会立即提交到数据库，而是暂时缓存。
            手动提交 / 回滚：
            所有操作成功后，需调用 self.db.commit() 提交事务，使所有操作生效。
            若中间出现错误，调用 self.db.rollback() 回滚事务，取消所有已执行的操作，
            恢复到事务开始前的状态。
            常用于需要批量执行多个 SQL 语句且要求整体生效的场景"""
        self.db.transaction()
        # 使用临时表重建数据（实现排序重置）
        try:
            # 开始事务
            self.db.transaction()

            # 使用临时表重建数据（实现排序重置）
            self.query.exec("DROP TABLE IF EXISTS temp_table")
            self.query.exec("CREATE TEMPORARY TABLE temp_table AS SELECT * FROM facedatabase")
            self.query.exec("DELETE FROM facedatabase")
            self.query.exec("""
                INSERT INTO facedatabase 
                (姓名, 性别, 年龄, 学号, 录入时间, 照片, 人脸特征)
                SELECT 姓名, 性别, 年龄, 学号, 录入时间, 照片, 人脸特征 FROM temp_table
            """)

            # 提交事务
            self.db.commit()

        except Exception as e:
            # 回滚事务
            self.db.rollback()
            print(f"排序更新失败: {e}")
        finally:
            # 清理临时表
            self.query.exec("DROP TABLE IF EXISTS temp_table")
            # 发射完成信号
            self.finished_signal.emit()

    def add_sql_cmd(self, cmd, content=None):
        """向队列添加SQL命令"""
        if content == None:
            content = {}
            #block让队列插入操作 “非阻塞”,满了不等待
        self.q_deal_sql_cmd.put({"cmd": cmd, "content":content},block=False)
        return True
        print("队列命令已满，无法添加新命令")
        return False


    def stop(self):
        self.running = False
        self.q_deal_sql_cmd.put(None)  # 唤醒队列阻塞
        self.wait()
        if self.db.isOpen():
            self.db.close()
